package nachos.threads;

import nachos.ag.BoatGrader;
import nachos.machine.Machine;

public class Boat
{
    static BoatGrader bg;
    static Lock lock;
    static Condition childWaitAtO;
    static Condition childWaitAtM;
    static Condition adultWaitAtO;
    static Condition adultWaitAtM;
    static Condition canGo;
    static int childNumO;
    static int adultNumO;
    static int childNumM;
    static int adultNumM;
    static boolean beginning;
    static boolean empty;

    public static void selfTest()
    {
        BoatGrader b = new BoatGrader();

//        System.out.println("\n ***Testing Boats with only 2 children***");
//        begin(0, 2, b);

//        System.out.println("\n ***Testing Boats with 2 children, 1 adult***");
//        begin(1, 2, b);

        System.out.println("\n ***Testing Boats with 3 children, 3 adults***");
        begin(3, 3, b);
    }

    public static void begin(int adults, int children, BoatGrader b)
    {
        // Store the externally generated autograder in a class
        // variable to be accessible by children.
        bg = b;

        // Instantiate global variables here
        lock = new Lock();
        childWaitAtO = new Condition(lock);
        childWaitAtM = new Condition(lock);
        adultWaitAtO = new Condition(lock);
        adultWaitAtM = new Condition(lock);
        canGo = new Condition(lock);
        childNumO = children;
        childNumM = 0;
        adultNumO = adults;
        adultNumM = 0;
        beginning = true;
        empty = true;

        // Create threads here. See section 3.4 of the Nachos for Java
        KThread t = new KThread();
        for(int i = 0; i < adults; i++)
        {
            t = new KThread(new Runnable()
            {
                @Override
                public void run()
                {
                    AdultItinerary();
                }
            });
            t.setName("Adult"+i);
            t.fork();
            KThread.yield();
        }
        for(int i = 0; i < children; i++)
        {
            t = new KThread(new Runnable()
            {
                @Override
                public void run()
                {
                    ChildItinerary();
                }
            });
            t.setName("Child"+i);
            t.fork();
            KThread.yield();
        }
        lock.acquire();
        if(adults > 0)
            adultWaitAtO.wake();
        else
            childWaitAtO.wake();
        lock.release();
        Alarm.alarm().waitUntil(8000);

        lock.acquire();
        if(childNumO == 0 && adultNumO == 0)
        {
            childWaitAtM.wakeAll();
            adultWaitAtM.wakeAll();
        }
        lock.release();
        KThread.yield();

        // Walkthrough linked from the projects page.

//        Runnable r = () -> SampleItinerary();
//        KThread t = new KThread(r);
//        t.setName("Sample Boat Thread");
//        t.fork();

    }

    static void AdultItinerary()
    {
        bg.initializeAdult(); //Required for autograder interface. Must be the first thing called.
        //DO NOT PUT ANYTHING ABOVE THIS LINE.

	/* This is where you should put your solutions. Make calls
	   to the BoatGrader to show that it is synchronized. For
	   example:
	       bg.AdultRowToMolokai();
	   indicates that an adult has rowed the boat across to Molokai
	*/
	    lock.acquire();
	    AdultSailOnce();
        lock.release();
    }

    static void AdultSailOnce()
    {
        adultWaitAtO.sleep();
        if(empty)
        {
            AdultPilot();

            if(childNumO > 0 || adultNumO > 0)
            {
                if(childNumO==1 && adultNumO == 0)
                {
                    childNumM--;
                    childWaitAtM.wake();
                    adultNumM++;
                    adultWaitAtM.sleep();
                }
                else if (adultNumM > 0)
                {
                    adultWaitAtM.wake();
                    adultWaitAtM.sleep();
                }
            }
        }
        else
        {
            canGo.sleep();
            bg.AdultRideToMolokai();

            lock.release();
            KThread.yield();
            lock.acquire();

            adultNumM++;
            adultWaitAtM.sleep();
        }


        //回
        if((childNumO > 0 || adultNumO > 0) && !(childNumO==1 && adultNumO == 0))
        {
            bg.AdultRowToOahu();
            adultNumO++;
            if(adultNumO > 0)
                adultWaitAtO.wake();
            else
                childWaitAtO.wake();
            empty = true;
            AdultSailOnce();

        }

    }

    static void AdultPilot()
    {
        empty = false;
        adultNumO--;
        if(adultNumO >= 1)
        {
            adultWaitAtO.wake();
            adultNumO--;
            if (childNumO >= 1)
            {
                childWaitAtO.wake();
                childNumO--;
            }
            else if(adultNumO >= 1)
            {
                adultWaitAtO.wake();
                adultNumO--;
            }
        }
        else if(childNumO >= 2)
        {
            childWaitAtO.wake();
            childWaitAtO.wake();
            childNumO -= 2;
        }

        lock.release();
        KThread.yield();
        lock.acquire();

        canGo.wakeAll();
        bg.AdultRowToMolokai();

        lock.release();
        KThread.yield();
        lock.acquire();
    }

    static void ChildItinerary()
    {
        bg.initializeChild(); //Required for autograder interface. Must be the first thing called.
        //DO NOT PUT ANYTHING ABOVE THIS LINE.
        lock.acquire();
        ChildSailOnce();
        lock.release();
    }

    static void ChildSailOnce()
    {
        childWaitAtO.sleep();

        if(empty)
        {
            ChildPilot();

            if(childNumO > 0 || adultNumO > 0)
            {
                if(adultNumO==1 && childNumM == 0)
                {
                    adultNumM--;
                    adultWaitAtM.wake();
                    childNumM++;
                    childWaitAtM.sleep();
                }
                else if (childNumM > 0)
                {
                    childWaitAtM.wake();
                    childWaitAtM.sleep();
                }
            }
        }
        else
        {
            canGo.sleep();
            bg.ChildRideToMolokai();

            lock.release();
            KThread.yield();
            lock.acquire();

            childNumM++;
            childWaitAtM.sleep();
        }


        //回
        if((childNumO > 0 || adultNumO > 0) && !(adultNumO==1 && childNumO == 0))
        {
            bg.ChildRowToOahu();
            childNumO++;
            if(adultNumO > 0)
                adultWaitAtO.wake();
            else
                childWaitAtO.wake();
            empty = true;
            ChildSailOnce();
        }

    }

    static void ChildPilot()
    {
        empty = false;
        childNumO--;
        if(childNumO >= 1)
        {
            childWaitAtO.wake();
            childNumO--;
            if (adultNumO >= 1)
            {
                adultWaitAtO.wake();
                adultNumO--;
            }
            else if(childNumO >= 1)
            {
                childWaitAtO.wake();
                childNumO--;
            }
        }
        else if(adultNumO >= 2)
        {
            adultWaitAtO.wake();
            adultWaitAtO.wake();
            adultNumO-=2;
        }

        lock.release();
        KThread.yield();
        lock.acquire();

        canGo.wakeAll();
        bg.ChildRowToMolokai();

        lock.release();
        KThread.yield();
        lock.acquire();
    }

    static void SampleItinerary()
    {
        // Please note that this isn't a valid solution (you can't fit
        // all of them on the boat). Please also note that you may not
        // have a single thread calculate a solution and then just play
        // it back at the autograder -- you will be caught.
        System.out.println("\n ***Everyone piles on the boat and goes to Molokai***");
        bg.AdultRowToMolokai();
        bg.ChildRideToMolokai();
        bg.AdultRideToMolokai();
        bg.ChildRideToMolokai();

        while(adultNumO > 0 || childNumO > 0)
        {
            if(beginning)
            {

            }
        }
    }

}
